Skip to content

Feat/analytics dashboard - #421

Merged
Benjtalkshow merged 5 commits into
boundlessfi:mainfrom
Sendi0011:feat/analytics-dashboard
Mar 4, 2026
Merged

Feat/analytics dashboard#421
Benjtalkshow merged 5 commits into
boundlessfi:mainfrom
Sendi0011:feat/analytics-dashboard

Conversation

@Sendi0011

@Sendi0011 Sendi0011 commented Mar 1, 2026

Copy link
Copy Markdown
Contributor

feat(analytics): implement /me/analytics personal growth dashboard — closes #397


Overview

This PR implements the personal analytics dashboard at app/me/analytics as specified in issue #397. The page is a full-width, interactive growth console that surfaces the authenticated user's platform stats and activity trends using data sourced exclusively from the existing session — no additional API calls are made on page load.


Changes

New files:

  • app/me/analytics/page.tsx — Route entry point, protected by AuthGuard, reads from user.profile populated by the existing getMe() call in useAuthStatus
  • components/analytics/AnalyticsBentoGrid.tsx — Asymmetric bento-style grid with staggered Framer Motion mount animation, surfacing all stats from GetMeResponse.stats with trend badges
  • components/analytics/AnalyticsChart.tsx — Recharts LineChart of GetMeResponse.chart with 7D / 30D / 90D / ALL range toggle, custom tooltip, and cyan→indigo gradient line
  • lib/utils/calculateTrend.ts — Pure utility functions for trend percentage calculation, chart data transformation, and range filtering. Independently testable with no React dependencies

How It Works

All data flows from user.profile which is set by useAuthStatus() via the already-existing getMe() fetch in hooks/use-auth.ts. The analytics page consumes this cached value — it introduces zero new network requests.

Trend percentages are calculated client-side by splitting the chart time series into two equal halves (previous period vs current period) and computing the percentage change. The calculateTrend utility handles all edge cases: zero previous-period values, null/undefined fields, and flat periods.

Chart data transformation (sorting by date, formatting labels) lives in transformChartData() and filterChartByRange() in the utility file — not inline in JSX.


Design

  • Bento grid uses an asymmetric 4-column layout — Global Reputation occupies a col-span-2 row-span-2 hero tile, with supporting metrics in smaller tiles around it
  • Cyan (#06b6d4) to Deep Indigo (#4f46e5) gradient applied to the chart line and active range toggle button, consistent with the Boundless palette
  • Glassmorphism tile treatment: bg-white/[0.03], border-white/[0.06], backdrop-blur-sm
  • Framer Motion stagger animation on bento tiles and fade-up on chart section on mount

Accessibility

  • Bento grid tiles are mirrored in a visually hidden <table> with proper column headers for screen readers
  • Chart section includes a sr-only data table fallback listing every date and activity count in the active range
  • Range toggle buttons use aria-pressed and role="group" with a descriptive label
  • Trend badges carry aria-label describing direction and percentage value

A Note on Chart Data

GetMeResponse.chart exposes a single { date, count } activity time series. The issue referenced a "Projects vs Earnings" multi-line chart, however no earnings field exists in the API response. Fabricating a second series would violate the issue's explicit requirement of no hardcoded or mock data. The chart renders the real activity series. If the backend adds additional series in future, AnalyticsChart is structured to accommodate extra <Line> entries with minimal changes.


Testing Checklist

  • npm run lint passes with no errors
  • npm run build passes with no breaking changes
  • No additional API calls on page load — verified via browser Network tab
  • Bento grid renders correctly across mobile, tablet, and desktop breakpoints
  • Trend badges display correct direction and color for each tile
  • Chart renders with cyan→indigo gradient line
  • Range toggle (7D / 30D / 90D / ALL) filters data and re-renders cleanly
  • Hover tooltips display correct values on chart
  • Chart and tiles animate on mount
  • Screen reader table fallbacks present in DOM
  • Edge cases tested: empty chart array, null stats, zero previous-period values

Screenshots

Desktop view

Screenshot 2026-02-28 at 23 48 36 Screenshot 2026-02-28 at 23 48 45

Mobile view

localhost_3000_me_analytics(iPhone 14 Pro Max) localhost_3000_me_analytics(iPhone 14 Pro Max) (5)

Summary by CodeRabbit

  • New Features
    • Analytics page now requires authentication and displays a guarded, client-rendered dashboard with loading and sign-in fallback.
    • Interactive analytics dashboard with animated metric tiles, trend badges, an activity heatmap, and a recent-activity chart.
    • Time-range filters (7D, 30D, 90D, All) for the chart with graceful empty-state handling and keyboard/ARIA support.
    • Accessibility enhancements including screen-reader summaries, ARIA labels, and smoother animations.

@Sendi0011
Sendi0011 requested a review from 0xdevcollins as a code owner March 1, 2026 00:33
@vercel

vercel Bot commented Mar 1, 2026

Copy link
Copy Markdown

@Sendi0011 is attempting to deploy a commit to the Threadflow Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Mar 1, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Replaces a redirect with a client-side, auth-guarded AnalyticsPage that reads session me data, shows loading/missing-data states, and renders new AnalyticsBentoGrid and AnalyticsChart using memoized trend and chart utilities. (50 words)

Changes

Cohort / File(s) Summary
Analytics Page & Auth Integration
app/me/analytics/page.tsx
Replaces simple redirect with AnalyticsPage default export. Adds AuthGuard wrapper, useAuthStatus usage, loading/fallback UI, validation for me.stats/me.chart, and composes AnalyticsBentoGrid + AnalyticsChart.
Bento Grid Component
components/analytics/AnalyticsBentoGrid.tsx
New client component AnalyticsBentoGrid. Renders animated, responsive bento grid of tiles with TrendBadge, screen-reader table, Framer Motion animations, memoized tile/trend calculations, and ARIA labels.
Chart Component
components/analytics/AnalyticsChart.tsx
New client component AnalyticsChart. Implements Recharts line chart with 7D/30D/90D/ALL range selector, data transform/filter utilities, custom tooltip, accessibility fallback, and empty-state handling.
Trend & Chart Utilities
lib/utils/calculateTrend.ts
New utilities exporting TrendResult, calculateTrend, calculateChartTrend, transformChartData, and filterChartByRange. Handles null/zero inputs, rounding, date labeling, and range slicing.

Sequence Diagram

sequenceDiagram
    participant User
    participant AnalyticsPage as AnalyticsPage
    participant AuthGuard
    participant Session
    participant AnalyticsContent
    participant BentoGrid as AnalyticsBentoGrid
    participant Chart as AnalyticsChart

    User->>AnalyticsPage: Visit /me/analytics
    AnalyticsPage->>AuthGuard: Render children
    AuthGuard->>Session: Read session / me
    alt Not authenticated
        AuthGuard->>User: Redirect to /sign-in
    else Authenticated
        AuthGuard->>AnalyticsContent: Render
        AnalyticsContent->>Session: Extract me.stats & me.chart
        AnalyticsContent->>User: Show loading spinner while auth loading
        alt No analytics data
            AnalyticsContent->>User: Show missing-data message
        else Has analytics data
            AnalyticsContent->>BentoGrid: Provide stats & chart
            AnalyticsContent->>Chart: Provide chart data
            BentoGrid->>lib/utils/calculateTrend: compute tile trends
            Chart->>lib/utils/calculateTrend: transform & filter chart data
            BentoGrid->>User: Render animated bento tiles
            Chart->>User: Render interactive line chart
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • 0xdevcollins
  • Benjtalkshow

Poem

🐰 In a meadow of metrics I hop with delight,
Tiles shimmer like dewdrops, trends take flight,
Charts hum the story of days that have been,
All pulled from the session — no calls in between,
Hop, click, explore — the dashboard feels right! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat/analytics dashboard' is clear and directly summarizes the main feature addition: an analytics dashboard implementation.
Linked Issues check ✅ Passed All major requirements from issue #397 are met: analytics dashboard at app/me/analytics with Bento-grid layout, interactive Recharts with 7D/30D/90D/ALL ranges, client-side trend calculations via calculateTrend utility, responsive design, accessibility features, and no additional API calls.
Out of Scope Changes check ✅ Passed All changes are in-scope: new analytics route, Bento grid component, chart component, and trend calculation utilities are all required for the analytics dashboard feature.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (5)
app/me/analytics/page.tsx (1)

11-12: Consider using const arrow functions per coding guidelines.

Both AnalyticsContent and AnalyticsPage are function declarations. The coding guidelines prefer const arrow functions with explicit type annotations.

♻️ Suggested refactor
-function AnalyticsContent() {
+const AnalyticsContent = (): JSX.Element => {
...
-export default function AnalyticsPage() {
+const AnalyticsPage = (): JSX.Element => {
...
+export default AnalyticsPage;

As per coding guidelines: "Prefer const arrow functions with explicit type annotations over function declarations".

Also applies to: 54-54

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/me/analytics/page.tsx` around lines 11 - 12, Convert the function
declarations AnalyticsContent and AnalyticsPage into const arrow functions with
explicit React/Next type annotations; replace "function AnalyticsContent() { ...
}" and "function AnalyticsPage() { ... }" with "const AnalyticsContent: React.FC
= () => { ... }" (or the appropriate NextPage/JSX.Element return type for
AnalyticsPage) and ensure any props or return types are explicitly annotated to
satisfy the coding guidelines.
components/analytics/AnalyticsChart.tsx (1)

82-95: Prefer clsx for conditional classes.

The conditional className uses a ternary operator within the template literal. The coding guidelines recommend using clsx or similar helper functions for cleaner conditional class composition.

♻️ Suggested refactor using clsx
+import clsx from 'clsx';
...
            <button
              key={r}
              onClick={() => setRange(r)}
              aria-pressed={range === r}
-             className={`rounded-lg px-3 py-1.5 text-xs font-medium transition-all duration-150 ${
-               range === r
-                 ? 'bg-gradient-to-r from-[`#06b6d4`] to-[`#4f46e5`] text-white shadow'
-                 : 'text-zinc-400 hover:text-white'
-             }`}
+             className={clsx(
+               'rounded-lg px-3 py-1.5 text-xs font-medium transition-all duration-150',
+               range === r
+                 ? 'bg-gradient-to-r from-[`#06b6d4`] to-[`#4f46e5`] text-white shadow'
+                 : 'text-zinc-400 hover:text-white'
+             )}
            >

As per coding guidelines: "For conditional classes, prefer clsx or similar helper functions over ternary operators in JSX".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/analytics/AnalyticsChart.tsx` around lines 82 - 95, Replace the
inline template-literal conditional classes in the AnalyticsChart component (the
ranges.map button render that calls setRange and reads range) with clsx: import
clsx at the top, then build the button className using clsx to always include
the shared classes ('rounded-lg px-3 py-1.5 text-xs font-medium transition-all
duration-150') and conditionally include the active classes ('bg-gradient-to-r
from-[`#06b6d4`] to-[`#4f46e5`] text-white shadow') when range === r, otherwise
include the inactive classes ('text-zinc-400 hover:text-white'); keep key,
onClick, aria-pressed and button children unchanged.
components/analytics/AnalyticsBentoGrid.tsx (1)

225-226: Consider using clsx for the conditional text size.

The ternary operator in the className could be replaced with clsx for consistency with coding guidelines.

♻️ Suggested refactor
+import clsx from 'clsx';
...
              <p
-               className={`font-bold tracking-tight text-white ${tile.large ? 'text-4xl' : 'text-2xl'}`}
+               className={clsx(
+                 'font-bold tracking-tight text-white',
+                 tile.large ? 'text-4xl' : 'text-2xl'
+               )}
              >

As per coding guidelines: "For conditional classes, prefer clsx or similar helper functions over ternary operators in JSX".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/analytics/AnalyticsBentoGrid.tsx` around lines 225 - 226, Replace
the inline ternary in the JSX className on the <p> element inside the
AnalyticsBentoGrid component with clsx: import clsx if not already imported,
then use clsx to combine the static classes "font-bold tracking-tight
text-white" with a conditional mapping for tile.large to choose "text-4xl" or
"text-2xl" (refer to the variable tile and the <p> element where className is
set) so the conditional class follows the project's clsx convention.
lib/utils/calculateTrend.ts (2)

56-65: Consider the fallback behavior when filtering yields no results.

When filtered is empty, the fallback data.slice(-days) returns the last N data points. This is reasonable, but if days exceeds data.length, it returns the entire array. Ensure this behavior aligns with UX expectations (e.g., showing "No data" vs. showing older data outside the requested range).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/utils/calculateTrend.ts` around lines 56 - 65, In filterChartByRange, the
current fallback returns data.slice(-days) which can return the entire array
when days > data.length; change the fallback so it returns an empty array when
the requested days exceeds data.length (so the caller can show "No data") and
otherwise return the last days entries. Concretely, replace the final return so
it uses filtered.length > 0 ? filtered : (days > data.length ? [] :
data.slice(-days)), keeping the function name filterChartByRange and variables
filtered, days and data as references.

6-9: Consider using const arrow functions per coding guidelines.

The coding guidelines prefer const arrow functions with explicit type annotations over function declarations. This is a stylistic preference and can be deferred.

♻️ Example refactor for one function
-export function calculateTrend(
-  current: number | null | undefined,
-  previous: number | null | undefined
-): TrendResult {
+export const calculateTrend = (
+  current: number | null | undefined,
+  previous: number | null | undefined
+): TrendResult => {

As per coding guidelines: "Prefer const arrow functions with explicit type annotations over function declarations".

Also applies to: 26-28, 39-41, 56-58

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/utils/calculateTrend.ts` around lines 6 - 9, Convert the function
declaration calculateTrend to an exported const arrow function with explicit
parameter and return type annotations (keep the same parameter names and
TrendResult return type) and replace the other function declarations in this
file with the same pattern; ensure you export the consts and preserve original
logic and symbol names (e.g., calculateTrend and the same parameter identifiers
and TrendResult) so tooling and callers remain unaffected.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/me/analytics/page.tsx`:
- Around line 11-12: Convert the function declarations AnalyticsContent and
AnalyticsPage into const arrow functions with explicit React/Next type
annotations; replace "function AnalyticsContent() { ... }" and "function
AnalyticsPage() { ... }" with "const AnalyticsContent: React.FC = () => { ... }"
(or the appropriate NextPage/JSX.Element return type for AnalyticsPage) and
ensure any props or return types are explicitly annotated to satisfy the coding
guidelines.

In `@components/analytics/AnalyticsBentoGrid.tsx`:
- Around line 225-226: Replace the inline ternary in the JSX className on the
<p> element inside the AnalyticsBentoGrid component with clsx: import clsx if
not already imported, then use clsx to combine the static classes "font-bold
tracking-tight text-white" with a conditional mapping for tile.large to choose
"text-4xl" or "text-2xl" (refer to the variable tile and the <p> element where
className is set) so the conditional class follows the project's clsx
convention.

In `@components/analytics/AnalyticsChart.tsx`:
- Around line 82-95: Replace the inline template-literal conditional classes in
the AnalyticsChart component (the ranges.map button render that calls setRange
and reads range) with clsx: import clsx at the top, then build the button
className using clsx to always include the shared classes ('rounded-lg px-3
py-1.5 text-xs font-medium transition-all duration-150') and conditionally
include the active classes ('bg-gradient-to-r from-[`#06b6d4`] to-[`#4f46e5`]
text-white shadow') when range === r, otherwise include the inactive classes
('text-zinc-400 hover:text-white'); keep key, onClick, aria-pressed and button
children unchanged.

In `@lib/utils/calculateTrend.ts`:
- Around line 56-65: In filterChartByRange, the current fallback returns
data.slice(-days) which can return the entire array when days > data.length;
change the fallback so it returns an empty array when the requested days exceeds
data.length (so the caller can show "No data") and otherwise return the last
days entries. Concretely, replace the final return so it uses filtered.length >
0 ? filtered : (days > data.length ? [] : data.slice(-days)), keeping the
function name filterChartByRange and variables filtered, days and data as
references.
- Around line 6-9: Convert the function declaration calculateTrend to an
exported const arrow function with explicit parameter and return type
annotations (keep the same parameter names and TrendResult return type) and
replace the other function declarations in this file with the same pattern;
ensure you export the consts and preserve original logic and symbol names (e.g.,
calculateTrend and the same parameter identifiers and TrendResult) so tooling
and callers remain unaffected.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4ec4081 and 3a59c4c.

📒 Files selected for processing (4)
  • app/me/analytics/page.tsx
  • components/analytics/AnalyticsBentoGrid.tsx
  • components/analytics/AnalyticsChart.tsx
  • lib/utils/calculateTrend.ts

@Benjtalkshow Benjtalkshow left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Sendi0011

Here are my correction

  1. Card Component Alignment
    I noticed that the analytics cards are using custom bento-style gradients (cyan and indigo). These should be aligned with our existing dashboard design.

    • Check the /me/earnings dashboard.
    • Use the standard Card component from components/ui/card for consistency.
  2. Gradient and Color Usage

    • You used skyblue and indigo gradients instead of our primary neon green.
    • Our platform's primary color is #a7f950.
    • Use the standard Tailwind utility classes:
      • text-primary
      • bg-primary
      • border-primary
    • Reference: These variables are already defined in globals.css and mapped to our brand identity.
  3. API Data Integration

    • Currently, only stats and chart objects from the API response are utilized.
    • For a full analytics dashboard, also integrate activitiesGraph and recentActivities.
    • Reuse these existing files to save time and maintain consistency:
      • components/profile/ActivityHeatMap.tsx: For the contribution graph (heatmap).
      • components/profile/ActivityFeed.tsx: For the recent activity list.
      • For "Activity Over Time" Section, reuse components/chart-area-interactive.tsx
  4. Page Layout and Styling

    • The page container is currently using a custom light background and border radius.
    • Follow the standard page layout structure.
    • Apply the following classes on your main wrapper to align with the sidebar and other dashboard pages:
    className="container mx-auto space-y-8 py-8 px-6"
  5. For Full Integration Testing

  • Go to /hackathons and join an ongoing hackathon.
  • Go to /projects, comment on projects, and vote on them.
  • Return to /me/analytics to verify that these actions reflect in the statistics and activity feeds.

Note: We prefer a video evidence

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (7)
components/analytics/AnalyticsBentoGrid.tsx (3)

27-59: Use typed const arrow functions for component declarations.

TrendBadge and AnalyticsBentoGrid should match the TSX function style expected by the repo.

♻️ Suggested refactor
-function TrendBadge({ trend }: { trend: TrendResult }) {
+const TrendBadge = ({
+  trend,
+}: {
+  trend: TrendResult;
+}): JSX.Element => {
...
-}
+};

-export function AnalyticsBentoGrid({ stats, chart }: Props) {
+export const AnalyticsBentoGrid = ({
+  stats,
+  chart,
+}: Props): JSX.Element => {
...
-}
+};

As per coding guidelines, "Prefer const arrow functions with explicit type annotations over function declarations".

Also applies to: 85-230

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/analytics/AnalyticsBentoGrid.tsx` around lines 27 - 59, Change the
component declarations from function declarations to typed const arrow
functions: replace the function TrendBadge({ trend }: { trend: TrendResult }) {
... } with a const TrendBadge: React.FC<{ trend: TrendResult }> = ({ trend }) =>
{ ... } (and do the same for AnalyticsBentoGrid, using an appropriate props
interface/type and explicit React.FC or React.FunctionComponent annotation).
Ensure all React imports/types remain available (e.g., React, TrendResult) and
update any exported names to use the const bindings so the components keep the
same external API.

87-89: flat object identity breaks tiles memoization effectiveness.

Because flat is recreated every render, Line 165 always changes and forces tiles recomputation.

♻️ Suggested refactor
+const FLAT_TREND: TrendResult = { percentage: 0, direction: 'flat' };
...
 export function AnalyticsBentoGrid({ stats, chart }: Props) {
   const chartTrend = useMemo(() => calculateChartTrend(chart), [chart]);
-  const flat: TrendResult = { percentage: 0, direction: 'flat' };
...
-        trend: flat,
+        trend: FLAT_TREND,
...
-        trend: flat,
+        trend: FLAT_TREND,
...
-        trend: flat,
+        trend: FLAT_TREND,
...
-        trend: flat,
+        trend: FLAT_TREND,
...
-        trend: flat,
+        trend: FLAT_TREND,
...
-        trend: flat,
+        trend: FLAT_TREND,
...
-        trend: flat,
+        trend: FLAT_TREND,
...
-    [stats, chartTrend, flat]
+    [stats, chartTrend]
   );

Also applies to: 165-166

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/analytics/AnalyticsBentoGrid.tsx` around lines 87 - 89, The local
constant flat (type TrendResult) is recreated each render which changes its
identity and forces the useMemo for tiles to recompute; fix this by giving flat
a stable identity—either hoist it to module scope as a top-level constant (e.g.,
FLAT: TrendResult) or memoize it inside the component with useMemo/useRef—then
reference that stable flat when building tiles in the tiles useMemo so tiles
only recomputes when its real dependencies change.

205-216: Replace class string interpolation/ternary with a class helper.

Line 205 and Line 216 should use clsx (or equivalent) rather than template/ternary class composition in JSX.

♻️ Suggested refactor
+import clsx from 'clsx';
...
-          <motion.div
+          <motion.div
             key={tile.label}
             variants={tileVariants}
-            className={`${tile.colSpan} ${tile.rowSpan}`}
+            className={clsx(tile.colSpan, tile.rowSpan)}
           >
...
-                <p
-                  className={`font-bold tracking-tight ${tile.large ? 'text-4xl' : 'text-2xl'}`}
-                >
+                <p
+                  className={clsx(
+                    'font-bold tracking-tight',
+                    tile.large ? 'text-4xl' : 'text-2xl'
+                  )}
+                >

As per coding guidelines, "For conditional classes, prefer clsx or similar helper functions over ternary operators in JSX".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/analytics/AnalyticsBentoGrid.tsx` around lines 205 - 216, Replace
the template/ternary class strings with a class helper (clsx): import clsx at
the top of the file, change the container className that uses `${tile.colSpan}
${tile.rowSpan}` to use clsx(tile.colSpan, tile.rowSpan), and change the
paragraph className that uses `font-bold tracking-tight ${tile.large ?
'text-4xl' : 'text-2xl'}` to use clsx('font-bold tracking-tight', tile.large ?
'text-4xl' : 'text-2xl'); ensure the rest of the JSX (Card, CardHeader,
CardContent, TrendBadge, tile.icon, tile.trend) is unaffected.
components/analytics/AnalyticsChart.tsx (2)

40-57: Convert function declarations to typed const arrow functions.

Both CustomTooltip and AnalyticsChart should follow the TS/TSX convention required in this repo.

♻️ Suggested refactor
-function CustomTooltip({
+const CustomTooltip = ({
   active,
   payload,
   label,
-}: TooltipProps<number, string>) {
+}: TooltipProps<number, string>): JSX.Element | null => {
   if (!active || !payload?.length) return null;
   return (
     <div className='border-border bg-card rounded-xl border px-4 py-3 shadow-xl'>
       <p className='text-muted-foreground mb-1 text-xs'>{label}</p>
       <p className='text-sm font-semibold'>
         {payload[0].value}{' '}
         <span className='text-muted-foreground font-normal'>activities</span>
       </p>
     </div>
   );
-}
+};

-export function AnalyticsChart({ chart }: Props) {
+export const AnalyticsChart = ({ chart }: Props): JSX.Element => {
   const [range, setRange] = useState<Range>('30D');
   const ranges: Range[] = ['7D', '30D', '90D', 'ALL'];
...
-}
+};

As per coding guidelines, "Prefer const arrow functions with explicit type annotations over function declarations".

Also applies to: 57-184

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/analytics/AnalyticsChart.tsx` around lines 40 - 57, Convert the
function declarations to const arrow functions with explicit type annotations:
replace "function CustomTooltip({...}: TooltipProps<number, string>)" with a
const arrow like "const CustomTooltip: React.FC<TooltipProps<number, string>> =
({...}) => { ... }" (preserve the active/payload/label handling and return JSX),
and replace "export function AnalyticsChart({ chart }: Props)" with "export
const AnalyticsChart: React.FC<Props> = ({ chart }) => { ... }" (keep existing
export, props usage, and component body). Ensure you import React types if
needed and maintain existing names and behavior for CustomTooltip and
AnalyticsChart so references elsewhere remain valid.

86-95: Use a named handle... click handler and a class helper for toggle buttons.

Line 89 and Line 91 currently use inline state mutation plus ternary class composition in JSX. This violates project conventions and is harder to maintain.

♻️ Suggested refactor
+import clsx from 'clsx';
...
 export function AnalyticsChart({ chart }: Props) {
   const [range, setRange] = useState<Range>('30D');
   const ranges: Range[] = ['7D', '30D', '90D', 'ALL'];
+  const handleRangeChange = (nextRange: Range): void => {
+    setRange(nextRange);
+  };
...
             {ranges.map(r => (
               <button
                 key={r}
-                onClick={() => setRange(r)}
+                onClick={() => handleRangeChange(r)}
                 aria-pressed={range === r}
-                className={`rounded-lg px-3 py-1.5 text-xs font-medium transition-all duration-150 ${
-                  range === r
-                    ? 'bg-primary text-primary-foreground shadow'
-                    : 'text-muted-foreground hover:text-foreground'
-                }`}
+                className={clsx(
+                  'rounded-lg px-3 py-1.5 text-xs font-medium transition-all duration-150',
+                  range === r
+                    ? 'bg-primary text-primary-foreground shadow'
+                    : 'text-muted-foreground hover:text-foreground'
+                )}
               >
                 {r}
               </button>
             ))}

As per coding guidelines, "For conditional classes, prefer clsx or similar helper functions over ternary operators in JSX" and "Event handlers should start with 'handle' prefix (e.g., handleClick, handleSubmit)".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/analytics/AnalyticsChart.tsx` around lines 86 - 95, Extract the
inline click handler and class ternary into named helpers: add a handler
function (e.g., handleRangeClick or handleSelectRange) that calls setRange(r)
and use it as the onClick in the ranges.map button, and replace the inline
ternary className with a clsx (or classNames) helper expression that composes
'rounded-lg px-3 py-1.5 text-xs font-medium transition-all duration-150' plus
either 'bg-primary text-primary-foreground shadow' when range === r or
'text-muted-foreground hover:text-foreground' otherwise; ensure you import clsx
(or your project's class helper) and keep aria-pressed={range === r} unchanged
so behavior and accessibility remain the same.
app/me/analytics/page.tsx (2)

104-113: Refactor filter buttons to use a handle... handler and class helper.

Line 107 and Line 109 currently use inline state updates and conditional class ternaries in JSX.

♻️ Suggested refactor
+import clsx from 'clsx';
...
 function AnalyticsContent() {
   const { user, isLoading } = useAuthStatus();
   const [activityFilter, setActivityFilter] = useState('All Time');
+  const handleActivityFilterChange = (filter: string): void => {
+    setActivityFilter(filter);
+  };
...
             {FILTER_OPTIONS.map(f => (
               <button
                 key={f}
-                onClick={() => setActivityFilter(f)}
+                onClick={() => handleActivityFilterChange(f)}
                 aria-pressed={activityFilter === f}
-                className={`rounded-lg px-3 py-1.5 text-xs font-medium transition-all duration-150 ${
-                  activityFilter === f
-                    ? 'bg-primary text-primary-foreground shadow'
-                    : 'text-muted-foreground hover:text-foreground'
-                }`}
+                className={clsx(
+                  'rounded-lg px-3 py-1.5 text-xs font-medium transition-all duration-150',
+                  activityFilter === f
+                    ? 'bg-primary text-primary-foreground shadow'
+                    : 'text-muted-foreground hover:text-foreground'
+                )}
               >
                 {f}
               </button>
             ))}

As per coding guidelines, "For conditional classes, prefer clsx or similar helper functions over ternary operators in JSX" and "Event handlers should start with 'handle' prefix (e.g., handleClick, handleSubmit)".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/me/analytics/page.tsx` around lines 104 - 113, Refactor the filter button
JSX to remove the inline arrow and ternary class logic: create a handler named
handleActivityFilterClick (or handleSetActivityFilter) that calls
setActivityFilter(f) and use that in onClick instead of () =>
setActivityFilter(f); replace the conditional className string for the button
with a clsx (or classNames) call to compose classes based on activityFilter ===
f; ensure you import clsx and update the button to reference the handler and the
composed class helper while keeping the existing keys (FILTER_OPTIONS,
activityFilter, setActivityFilter) intact.

30-37: Use typed const arrow components instead of function declarations.

AnalyticsContent and AnalyticsPage should follow the project TSX component declaration convention.

♻️ Suggested refactor
-function AnalyticsContent() {
+const AnalyticsContent = (): JSX.Element => {
...
-}
+};

-export default function AnalyticsPage() {
-  return (
+const AnalyticsPage = (): JSX.Element => {
+  return (
     <AuthGuard
       redirectTo='/auth?mode=signin'
       fallback={<div className='p-8 text-center'>Authenticating...</div>}
     >
       <AnalyticsContent />
     </AuthGuard>
   );
-}
+};
+
+export default AnalyticsPage;

As per coding guidelines, "Prefer const arrow functions with explicit type annotations over function declarations".

Also applies to: 128-137

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/me/analytics/page.tsx` around lines 30 - 37, Convert the two function
declarations into typed const arrow components: replace the function declaration
for AnalyticsContent with a const declaration (e.g. const AnalyticsContent:
React.FC or React.FunctionComponent = () => { ... }) and do the same for
AnalyticsPage (the component referenced later around the other occurrence).
Ensure you keep the existing props/types (use GetMeResponse usage) and any
hooks/returns intact, add the appropriate React import if missing, and export
the default component the same way as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/me/analytics/page.tsx`:
- Around line 104-113: Refactor the filter button JSX to remove the inline arrow
and ternary class logic: create a handler named handleActivityFilterClick (or
handleSetActivityFilter) that calls setActivityFilter(f) and use that in onClick
instead of () => setActivityFilter(f); replace the conditional className string
for the button with a clsx (or classNames) call to compose classes based on
activityFilter === f; ensure you import clsx and update the button to reference
the handler and the composed class helper while keeping the existing keys
(FILTER_OPTIONS, activityFilter, setActivityFilter) intact.
- Around line 30-37: Convert the two function declarations into typed const
arrow components: replace the function declaration for AnalyticsContent with a
const declaration (e.g. const AnalyticsContent: React.FC or
React.FunctionComponent = () => { ... }) and do the same for AnalyticsPage (the
component referenced later around the other occurrence). Ensure you keep the
existing props/types (use GetMeResponse usage) and any hooks/returns intact, add
the appropriate React import if missing, and export the default component the
same way as before.

In `@components/analytics/AnalyticsBentoGrid.tsx`:
- Around line 27-59: Change the component declarations from function
declarations to typed const arrow functions: replace the function TrendBadge({
trend }: { trend: TrendResult }) { ... } with a const TrendBadge: React.FC<{
trend: TrendResult }> = ({ trend }) => { ... } (and do the same for
AnalyticsBentoGrid, using an appropriate props interface/type and explicit
React.FC or React.FunctionComponent annotation). Ensure all React imports/types
remain available (e.g., React, TrendResult) and update any exported names to use
the const bindings so the components keep the same external API.
- Around line 87-89: The local constant flat (type TrendResult) is recreated
each render which changes its identity and forces the useMemo for tiles to
recompute; fix this by giving flat a stable identity—either hoist it to module
scope as a top-level constant (e.g., FLAT: TrendResult) or memoize it inside the
component with useMemo/useRef—then reference that stable flat when building
tiles in the tiles useMemo so tiles only recomputes when its real dependencies
change.
- Around line 205-216: Replace the template/ternary class strings with a class
helper (clsx): import clsx at the top of the file, change the container
className that uses `${tile.colSpan} ${tile.rowSpan}` to use clsx(tile.colSpan,
tile.rowSpan), and change the paragraph className that uses `font-bold
tracking-tight ${tile.large ? 'text-4xl' : 'text-2xl'}` to use clsx('font-bold
tracking-tight', tile.large ? 'text-4xl' : 'text-2xl'); ensure the rest of the
JSX (Card, CardHeader, CardContent, TrendBadge, tile.icon, tile.trend) is
unaffected.

In `@components/analytics/AnalyticsChart.tsx`:
- Around line 40-57: Convert the function declarations to const arrow functions
with explicit type annotations: replace "function CustomTooltip({...}:
TooltipProps<number, string>)" with a const arrow like "const CustomTooltip:
React.FC<TooltipProps<number, string>> = ({...}) => { ... }" (preserve the
active/payload/label handling and return JSX), and replace "export function
AnalyticsChart({ chart }: Props)" with "export const AnalyticsChart:
React.FC<Props> = ({ chart }) => { ... }" (keep existing export, props usage,
and component body). Ensure you import React types if needed and maintain
existing names and behavior for CustomTooltip and AnalyticsChart so references
elsewhere remain valid.
- Around line 86-95: Extract the inline click handler and class ternary into
named helpers: add a handler function (e.g., handleRangeClick or
handleSelectRange) that calls setRange(r) and use it as the onClick in the
ranges.map button, and replace the inline ternary className with a clsx (or
classNames) helper expression that composes 'rounded-lg px-3 py-1.5 text-xs
font-medium transition-all duration-150' plus either 'bg-primary
text-primary-foreground shadow' when range === r or 'text-muted-foreground
hover:text-foreground' otherwise; ensure you import clsx (or your project's
class helper) and keep aria-pressed={range === r} unchanged so behavior and
accessibility remain the same.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3a59c4c and 77558ed.

📒 Files selected for processing (3)
  • app/me/analytics/page.tsx
  • components/analytics/AnalyticsBentoGrid.tsx
  • components/analytics/AnalyticsChart.tsx

@Sendi0011
Sendi0011 force-pushed the feat/analytics-dashboard branch from 77558ed to 8690841 Compare March 3, 2026 16:08

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (8)
lib/utils/calculateTrend.ts (1)

6-65: Apply typed const-arrow function style for exported TS utilities.

The module uses function declarations for exported helpers; repo convention prefers typed const-arrow exports.

As per coding guidelines, "Prefer const arrow functions with explicit type annotations over function declarations".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/utils/calculateTrend.ts` around lines 6 - 65, Convert the exported
function declarations to typed const arrow functions: replace the function
declarations for calculateTrend, calculateChartTrend, transformChartData, and
filterChartByRange with exported consts using arrow functions and explicit
return types (e.g., export const calculateTrend = (current: number | null |
undefined, previous: number | null | undefined): TrendResult => { ... }),
preserving the existing logic and signatures (including TrendResult and
parameter types) and keep helper internals and defaulting behavior unchanged;
ensure all export names remain identical so call sites are unaffected.
components/analytics/AnalyticsBentoGrid.tsx (3)

27-59: Adopt typed const-arrow declarations for component functions in this TSX file.

TrendBadge and AnalyticsBentoGrid are function declarations; repository style asks for typed const-arrow declarations.

As per coding guidelines, "Prefer const arrow functions with explicit type annotations over function declarations".

Also applies to: 85-230

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/analytics/AnalyticsBentoGrid.tsx` around lines 27 - 59, The file
uses function declarations for React components (e.g., TrendBadge and
AnalyticsBentoGrid); convert them to typed const-arrow components with explicit
React.FC (or appropriate prop) type annotations — replace "function TrendBadge({
trend }: { trend: TrendResult })" with a const declaration like "const
TrendBadge: React.FC<{ trend: TrendResult }> = ({ trend }) => { ... }" (and do
the same for AnalyticsBentoGrid and any other component functions in the range),
ensuring you export/keep names and behaviors unchanged and update any internal
references accordingly.

87-89: Stabilize flat trend reference to avoid defeating useMemo dependencies.

Line 87 recreates flat on every render; Line 165 then treats it as a dependency, forcing tiles recomputation each render.

♻️ Suggested refactor
+const FLAT_TREND: TrendResult = { percentage: 0, direction: 'flat' };
...
 export function AnalyticsBentoGrid({ stats, chart }: Props) {
   const chartTrend = useMemo(() => calculateChartTrend(chart), [chart]);
-  const flat: TrendResult = { percentage: 0, direction: 'flat' };

   const tiles: TileConfig[] = useMemo(
     () => [
       ...
-        trend: flat,
+        trend: FLAT_TREND,
       ...
     ],
-    [stats, chartTrend, flat]
+    [stats, chartTrend]
   );

Also applies to: 165-166

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/analytics/AnalyticsBentoGrid.tsx` around lines 87 - 89, The local
const flat (TrendResult) is recreated each render, breaking memoization for
tiles (useMemo depends on flat); stabilize its reference by moving it out of the
render or memoizing it — e.g., define a module-level constant (FLAT: TrendResult
= Object.freeze({ percentage: 0, direction: 'flat' })) or wrap flat in
React.useMemo(() => ({ percentage:0, direction:'flat' }), []) and then use that
stable identifier in the tiles useMemo dependency list (update usages at the
symbols flat and tiles accordingly).

205-216: Use clsx for conditional class composition in tile containers/text sizes.

Lines 205 and 216 use template-string interpolation for conditional classes instead of a class helper.

As per coding guidelines, "For conditional classes, prefer clsx or similar helper functions over ternary operators in JSX".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/analytics/AnalyticsBentoGrid.tsx` around lines 205 - 216, The tile
container and text-size class usages in AnalyticsBentoGrid currently use
template strings for conditional classes; import clsx (or classNames) and
replace the two spots: the outer element's className that currently interpolates
`${tile.colSpan} ${tile.rowSpan}` should become className={clsx(tile.colSpan,
tile.rowSpan)}, and the paragraph's className that uses `font-bold
tracking-tight ${tile.large ? 'text-4xl' : 'text-2xl'}` should become
className={clsx('font-bold tracking-tight', tile.large ? 'text-4xl' :
'text-2xl')}; ensure you add the clsx import at the top of the file.
components/analytics/AnalyticsChart.tsx (2)

87-95: Refactor range buttons to use handle* handler naming and clsx for conditional classes.

Line 89 uses an inline handler and Lines 91-95 use ternary string classes; this diverges from repo conventions and makes button styling logic harder to scale.

♻️ Suggested refactor
+import clsx from 'clsx';
...
 export function AnalyticsChart({ chart }: Props) {
   const [range, setRange] = useState<Range>('30D');
   const ranges: Range[] = ['7D', '30D', '90D', 'ALL'];
+  const handleRangeChange = (nextRange: Range): void => setRange(nextRange);
...
             {ranges.map(r => (
               <button
                 key={r}
-                onClick={() => setRange(r)}
+                type='button'
+                onClick={() => handleRangeChange(r)}
                 aria-pressed={range === r}
-                className={`rounded-lg px-3 py-1.5 text-xs font-medium transition-all duration-150 ${
-                  range === r
-                    ? 'bg-primary text-primary-foreground shadow'
-                    : 'text-muted-foreground hover:text-foreground'
-                }`}
+                className={clsx(
+                  'rounded-lg px-3 py-1.5 text-xs font-medium transition-all duration-150',
+                  range === r
+                    ? 'bg-primary text-primary-foreground shadow'
+                    : 'text-muted-foreground hover:text-foreground'
+                )}
               >
                 {r}
               </button>
             ))}

As per coding guidelines, "For conditional classes, prefer clsx or similar helper functions over ternary operators in JSX" and "Event handlers should start with 'handle' prefix (e.g., handleClick, handleSubmit)".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/analytics/AnalyticsChart.tsx` around lines 87 - 95, The range
buttons use an inline onClick and ternary string concatenation for className;
refactor by extracting the click logic into a named handler (e.g., create a
function handleRangeChange or handleRangeClick that calls setRange) and replace
the inline onClick with that handler, and switch the className expression to use
clsx (import clsx if not present) to conditionally apply 'bg-primary
text-primary-foreground shadow' when range === r and the muted/hover classes
otherwise; update any typings/props if needed and ensure aria-pressed still uses
range === r.

40-57: Use typed const arrow functions instead of function declarations in this TSX module.

CustomTooltip and AnalyticsChart are declared with function; repository convention asks for typed const-arrow declarations.

♻️ Suggested refactor
-function CustomTooltip({
+const CustomTooltip = ({
   active,
   payload,
   label,
-}: TooltipProps<number, string>) {
+}: TooltipProps<number, string>): JSX.Element | null => {
   if (!active || !payload?.length) return null;
   return (
     <div className='border-border bg-card rounded-xl border px-4 py-3 shadow-xl'>
       ...
     </div>
   );
-}
+};

-export function AnalyticsChart({ chart }: Props) {
+export const AnalyticsChart = ({ chart }: Props): JSX.Element => {
   ...
-}
+};

As per coding guidelines, "Prefer const arrow functions with explicit type annotations over function declarations".

Also applies to: 57-184

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/analytics/AnalyticsChart.tsx` around lines 40 - 57, Replace the
function declarations with typed const-arrow components: change CustomTooltip to
a const arrow with an explicit type annotation (e.g., const CustomTooltip = ({
active, payload, label }: TooltipProps<number, string>): JSX.Element | null => {
... }) and change AnalyticsChart to an exported const arrow with Props typed
(e.g., export const AnalyticsChart = ({ chart }: Props): JSX.Element => { ...
}); preserve existing logic/JSX and imports, and apply the same refactor for
other function declarations in this module (around the 57-184 range) to match
the repository convention.
app/me/analytics/page.tsx (2)

105-113: Refactor activity-filter buttons to use handle* handlers and clsx.

Line 107 uses inline click logic, and Lines 109-113 use ternary class composition; both conflict with repo conventions.

As per coding guidelines, "For conditional classes, prefer clsx or similar helper functions over ternary operators in JSX" and "Event handlers should start with 'handle' prefix (e.g., handleClick, handleSubmit)".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/me/analytics/page.tsx` around lines 105 - 113, Replace the inline click
handler and ternary className on the activity filter buttons with a named
handler and clsx usage: create a handler named handleActivityFilterClick (or
similar) that calls setActivityFilter(f) and use it as the button's onClick, and
replace the template literal/ternary in className with clsx(...) combining the
base classes and conditional classes using activityFilter === f; reference the
existing setActivityFilter, activityFilter and the button render that uses
key={f} and f to locate where to change.

30-126: Use typed const-arrow components for AnalyticsContent and AnalyticsPage.

Both components are declared with function syntax; repository standard prefers typed const-arrow declarations.

As per coding guidelines, "Prefer const arrow functions with explicit type annotations over function declarations".

Also applies to: 128-137

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/me/analytics/page.tsx` around lines 30 - 126, Convert the function
declarations to typed const-arrow components: replace "function
AnalyticsContent()" with "const AnalyticsContent: React.FC = () => { ... }"
(keeping all existing hooks/useMemo/useState and return JSX intact), and replace
the page export (currently "function AnalyticsPage" or default function) with
"const AnalyticsPage: React.FC = () => { ... }" and export default
AnalyticsPage; ensure you import React types if needed and keep component names
AnalyticsContent and AnalyticsPage unchanged so references (AnalyticsBentoGrid,
ActivityFeed, etc.) continue to work.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/me/analytics/page.tsx`:
- Line 55: Replace the activities source with meData.recentActivities by
changing the activities declaration that currently uses meData.user?.activities
to use meData.recentActivities (cast to Activity[]), update the ActivityFeed
invocation to accept an explicit activities prop (pass activities into the
ActivityFeed component instead of relying on it to derive from
user.user.activities), and modify the ActivityFeed component signature/prop
usage to accept and use the incoming activities prop rather than pulling
activities from the nested user object.

In `@lib/utils/calculateTrend.ts`:
- Around line 30-35: The midpoint split in calculateTrend.ts currently slices
the incoming data array (variables mid, previous, current) without guaranteeing
chronological order, which can flip trend direction; before computing mid and
slicing, sort the data array in ascending order by its date/timestamp field
(e.g., data.sort((a,b) => new Date(a.date) - new Date(b.date)) or by numeric
timestamp) so that previous and current windows represent earlier vs later
periods, then compute mid, previous, current and call calculateTrend as before.
- Around line 61-64: The cutoff uses the current timestamp so entries on the
cutoff day can be excluded; modify the logic in calculateTrend (the cutoff
variable and the filtering for filtered) to normalize both cutoff and each data
date to day boundaries (e.g., setHours(0,0,0,0) or create UTC-midnight dates)
before comparing, i.e., compute cutoffStart = (new Date()).setHours(0,0,0,0) and
compare against each data date normalized to its start-of-day (normalize new
Date(d.date) similarly) so boundary-day entries are included correctly; keep the
same fallback (data.slice(-days)) behavior.

---

Nitpick comments:
In `@app/me/analytics/page.tsx`:
- Around line 105-113: Replace the inline click handler and ternary className on
the activity filter buttons with a named handler and clsx usage: create a
handler named handleActivityFilterClick (or similar) that calls
setActivityFilter(f) and use it as the button's onClick, and replace the
template literal/ternary in className with clsx(...) combining the base classes
and conditional classes using activityFilter === f; reference the existing
setActivityFilter, activityFilter and the button render that uses key={f} and f
to locate where to change.
- Around line 30-126: Convert the function declarations to typed const-arrow
components: replace "function AnalyticsContent()" with "const AnalyticsContent:
React.FC = () => { ... }" (keeping all existing hooks/useMemo/useState and
return JSX intact), and replace the page export (currently "function
AnalyticsPage" or default function) with "const AnalyticsPage: React.FC = () =>
{ ... }" and export default AnalyticsPage; ensure you import React types if
needed and keep component names AnalyticsContent and AnalyticsPage unchanged so
references (AnalyticsBentoGrid, ActivityFeed, etc.) continue to work.

In `@components/analytics/AnalyticsBentoGrid.tsx`:
- Around line 27-59: The file uses function declarations for React components
(e.g., TrendBadge and AnalyticsBentoGrid); convert them to typed const-arrow
components with explicit React.FC (or appropriate prop) type annotations —
replace "function TrendBadge({ trend }: { trend: TrendResult })" with a const
declaration like "const TrendBadge: React.FC<{ trend: TrendResult }> = ({ trend
}) => { ... }" (and do the same for AnalyticsBentoGrid and any other component
functions in the range), ensuring you export/keep names and behaviors unchanged
and update any internal references accordingly.
- Around line 87-89: The local const flat (TrendResult) is recreated each
render, breaking memoization for tiles (useMemo depends on flat); stabilize its
reference by moving it out of the render or memoizing it — e.g., define a
module-level constant (FLAT: TrendResult = Object.freeze({ percentage: 0,
direction: 'flat' })) or wrap flat in React.useMemo(() => ({ percentage:0,
direction:'flat' }), []) and then use that stable identifier in the tiles
useMemo dependency list (update usages at the symbols flat and tiles
accordingly).
- Around line 205-216: The tile container and text-size class usages in
AnalyticsBentoGrid currently use template strings for conditional classes;
import clsx (or classNames) and replace the two spots: the outer element's
className that currently interpolates `${tile.colSpan} ${tile.rowSpan}` should
become className={clsx(tile.colSpan, tile.rowSpan)}, and the paragraph's
className that uses `font-bold tracking-tight ${tile.large ? 'text-4xl' :
'text-2xl'}` should become className={clsx('font-bold tracking-tight',
tile.large ? 'text-4xl' : 'text-2xl')}; ensure you add the clsx import at the
top of the file.

In `@components/analytics/AnalyticsChart.tsx`:
- Around line 87-95: The range buttons use an inline onClick and ternary string
concatenation for className; refactor by extracting the click logic into a named
handler (e.g., create a function handleRangeChange or handleRangeClick that
calls setRange) and replace the inline onClick with that handler, and switch the
className expression to use clsx (import clsx if not present) to conditionally
apply 'bg-primary text-primary-foreground shadow' when range === r and the
muted/hover classes otherwise; update any typings/props if needed and ensure
aria-pressed still uses range === r.
- Around line 40-57: Replace the function declarations with typed const-arrow
components: change CustomTooltip to a const arrow with an explicit type
annotation (e.g., const CustomTooltip = ({ active, payload, label }:
TooltipProps<number, string>): JSX.Element | null => { ... }) and change
AnalyticsChart to an exported const arrow with Props typed (e.g., export const
AnalyticsChart = ({ chart }: Props): JSX.Element => { ... }); preserve existing
logic/JSX and imports, and apply the same refactor for other function
declarations in this module (around the 57-184 range) to match the repository
convention.

In `@lib/utils/calculateTrend.ts`:
- Around line 6-65: Convert the exported function declarations to typed const
arrow functions: replace the function declarations for calculateTrend,
calculateChartTrend, transformChartData, and filterChartByRange with exported
consts using arrow functions and explicit return types (e.g., export const
calculateTrend = (current: number | null | undefined, previous: number | null |
undefined): TrendResult => { ... }), preserving the existing logic and
signatures (including TrendResult and parameter types) and keep helper internals
and defaulting behavior unchanged; ensure all export names remain identical so
call sites are unaffected.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 77558ed and 8690841.

📒 Files selected for processing (4)
  • app/me/analytics/page.tsx
  • components/analytics/AnalyticsBentoGrid.tsx
  • components/analytics/AnalyticsChart.tsx
  • lib/utils/calculateTrend.ts

Comment thread app/me/analytics/page.tsx
);
}

const activities = (meData.user?.activities ?? []) as Activity[];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify activity field shape and usage sites
set -euo pipefail

echo "== GetMeResponse / User shape =="
rg -n -C3 'interface GetMeResponse|interface User|recentActivities|activities' lib/api/types.ts

echo
echo "== Analytics page activity source =="
rg -n -C3 'meData\.user\?\.activities|recentActivities|ActivityHeatmap|ActivityFeed' app/me/analytics/page.tsx

echo
echo "== ActivityFeed expected input shape =="
rg -n -C3 'type ActivityFeedProps|user\.user\.activities|recentActivities' components/profile/ActivityFeed.tsx

Repository: boundlessfi/boundless

Length of output: 3578


Use meData.recentActivities instead of meData.user?.activities to prevent silently empty activity widgets.

Line 55 sources activities from meData.user?.activities, but GetMeResponse explicitly provides recentActivities at the top level. If the backend populates only recentActivities and omits the nested user.activities field, both the heatmap and feed render empty despite valid analytics data in the response. The comment on line 77 ("uses recentActivities") also contradicts the actual code path.

Replace line 55 with:

const activities = (meData.recentActivities ?? []) as Activity[];

Also update line 121 to pass activities directly to ActivityFeed:

<ActivityFeed filter={activityFilter} activities={activities} />

Then verify ActivityFeed component accepts an activities prop instead of deriving it from user.user.activities.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/me/analytics/page.tsx` at line 55, Replace the activities source with
meData.recentActivities by changing the activities declaration that currently
uses meData.user?.activities to use meData.recentActivities (cast to
Activity[]), update the ActivityFeed invocation to accept an explicit activities
prop (pass activities into the ActivityFeed component instead of relying on it
to derive from user.user.activities), and modify the ActivityFeed component
signature/prop usage to accept and use the incoming activities prop rather than
pulling activities from the nested user object.

Comment on lines +30 to +35
const mid = Math.floor(data.length / 2);
const previous = data
.slice(0, mid)
.reduce((sum, d) => sum + (d.count ?? 0), 0);
const current = data.slice(mid).reduce((sum, d) => sum + (d.count ?? 0), 0);
return calculateTrend(current, previous);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Sort chart data before midpoint split to avoid incorrect trend direction/percentage.

Line 30 currently splits by array position. If API order is not strictly ascending by date, previous vs current windows become wrong.

🐛 Suggested fix
 export function calculateChartTrend(
   data: Array<{ date: string; count: number }> | null | undefined
 ): TrendResult {
   if (!data || data.length === 0) return { percentage: 0, direction: 'flat' };
-  const mid = Math.floor(data.length / 2);
-  const previous = data
+  const sorted = [...data].sort(
+    (a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
+  );
+  const mid = Math.floor(sorted.length / 2);
+  const previous = sorted
     .slice(0, mid)
     .reduce((sum, d) => sum + (d.count ?? 0), 0);
-  const current = data.slice(mid).reduce((sum, d) => sum + (d.count ?? 0), 0);
+  const current = sorted
+    .slice(mid)
+    .reduce((sum, d) => sum + (d.count ?? 0), 0);
   return calculateTrend(current, previous);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/utils/calculateTrend.ts` around lines 30 - 35, The midpoint split in
calculateTrend.ts currently slices the incoming data array (variables mid,
previous, current) without guaranteeing chronological order, which can flip
trend direction; before computing mid and slicing, sort the data array in
ascending order by its date/timestamp field (e.g., data.sort((a,b) => new
Date(a.date) - new Date(b.date)) or by numeric timestamp) so that previous and
current windows represent earlier vs later periods, then compute mid, previous,
current and call calculateTrend as before.

Comment on lines +61 to +64
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - days);
const filtered = data.filter(d => new Date(d.date) >= cutoff);
return filtered.length > 0 ? filtered : data.slice(-days);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Normalize cutoff/data dates to day boundaries for range filtering.

Line 61 uses the current timestamp (hour/minute included), so boundary-day entries can be excluded depending on timezone/time-of-day.

🐛 Suggested fix
   if (days === 'ALL' || data.length === 0) return data;
   const cutoff = new Date();
+  cutoff.setHours(0, 0, 0, 0);
   cutoff.setDate(cutoff.getDate() - days);
-  const filtered = data.filter(d => new Date(d.date) >= cutoff);
+  const filtered = data.filter(d => {
+    const day = new Date(d.date);
+    day.setHours(0, 0, 0, 0);
+    return day >= cutoff;
+  });
   return filtered.length > 0 ? filtered : data.slice(-days);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - days);
const filtered = data.filter(d => new Date(d.date) >= cutoff);
return filtered.length > 0 ? filtered : data.slice(-days);
const cutoff = new Date();
cutoff.setHours(0, 0, 0, 0);
cutoff.setDate(cutoff.getDate() - days);
const filtered = data.filter(d => {
const day = new Date(d.date);
day.setHours(0, 0, 0, 0);
return day >= cutoff;
});
return filtered.length > 0 ? filtered : data.slice(-days);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/utils/calculateTrend.ts` around lines 61 - 64, The cutoff uses the
current timestamp so entries on the cutoff day can be excluded; modify the logic
in calculateTrend (the cutoff variable and the filtering for filtered) to
normalize both cutoff and each data date to day boundaries (e.g.,
setHours(0,0,0,0) or create UTC-midnight dates) before comparing, i.e., compute
cutoffStart = (new Date()).setHours(0,0,0,0) and compare against each data date
normalized to its start-of-day (normalize new Date(d.date) similarly) so
boundary-day entries are included correctly; keep the same fallback
(data.slice(-days)) behavior.

@Sendi0011

Copy link
Copy Markdown
Contributor Author

Gm @Benjtalkshow trust you are doing great today?

After testing thoroughly on staging, here’s what I found:

  • PROJECT_FOLLOWED and USER_FOLLOWED actions are working correctly. They show up in the activity feed and heatmap as expected.
  • Hackathon joins, comments, and votes are not being reflected. They return 0 in stats and don’t appear in user.activities or hackathonSubmissionsAsParticipant. I confirmed this by calling /users/me directly after performing each action.
  • The chart endpoint returns 91 data points, but all counts are 0, which suggests the staging backend isn’t aggregating these activity types.

This looks like a backend/staging data issue and not related to this PR. The frontend is correctly displaying whatever the API returns.

I’ll drop the video proof shortly.

@Benjtalkshow
Benjtalkshow merged commit 5eda51b into boundlessfi:main Mar 4, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implementation of "Analytics" Page

2 participants